package com.gitee.huanminabc.utils_tools.security;

import com.gitee.huanminabc.utils_tools.security.access.SecurityAccessDeniedHandler;
import com.gitee.huanminabc.utils_tools.security.access.SecurityAuthenticationEntryPoint;
import com.gitee.huanminabc.utils_tools.security.access.SecurityAuthenticationFailureHandler;
import com.gitee.huanminabc.utils_tools.security.access.SecurityAuthenticationSuccessHandler;
import com.gitee.huanminabc.utils_tools.security.aspect.Anonymous;
import com.gitee.huanminabc.utils_tools.security.core.AuthorityResource;
import com.gitee.huanminabc.utils_tools.security.core.MD5PasswordEncoder;
import com.gitee.huanminabc.utils_tools.security.core.SecurityJwtAuthenticationTokenFilter;
import com.gitee.huanminabc.utils_tools.spring_base.utils.SpringContextUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationDetailsSource;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.WebAuthenticationDetails;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

import javax.servlet.http.HttpServletRequest;
import java.util.*;

@Configuration
@ComponentScan("com.gitee.huanminabc.utils_tools.security")
public class WebSecurityConfigurer extends WebSecurityConfigurerAdapter  {
    public static  List<RequestMatcher> requestMatcher = new ArrayList<>();
    //token认证过滤器
    @Autowired
    private SecurityJwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;

    // 自定义未登录时：返回状态码401
    @Autowired
    private SecurityAuthenticationEntryPoint authenticationEntryPoint;

    // 自定义权限不足处理器：返回状态码403
    @Autowired
    private SecurityAccessDeniedHandler accessDeniedHandler;

    //自定义登录失败处理器：返回状态码402
    @Autowired
    private SecurityAuthenticationFailureHandler failureHandler;


    //自定义登录成功处理器：返回状态码200
    @Autowired
    private SecurityAuthenticationSuccessHandler successHandler;






    @Autowired
    private AuthenticationDetailsSource<HttpServletRequest, WebAuthenticationDetails> authenticationDetailsSource; //身份验证详细信息源

    @Autowired
    private RequestMappingHandlerMapping requestMappingHandlerMapping;

    /**
     * 密码机密处理器
     * <p>
     * 将BCryptPasswordEncoder对象注入到spring容器中,更换掉原来的 PasswordEncoder加密方式
     * 原PasswordEncoder密码格式为：{id}password。它会根据id去判断密码的加密方式。
     * 如果没替换原来的加密方式，数据库中想用明文密码做测试，将密码字段改为{noop}123456这样的格式
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new MD5PasswordEncoder();
    }

    /**
     * 注入AuthenticationManager 进行用户认证
     */
    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }
    @Override
    public void configure(WebSecurity web) throws Exception {
        // 不受权限控制的url
        Set<String> authorityUrls = getAuthorityUrls();
        List<String> urlAll = new ArrayList<>(authorityUrls);
        urlAll.addAll(anonymousUrlAll());
        String[] urlArrayAll = urlAll.toArray(new String[urlAll.size()]);
        WebSecurityConfigurer.requestMatcher = createRequestMatcher(authorityUrls);
        web.ignoring().antMatchers(urlArrayAll);
    }

    /**
     * 认证配置
     * anonymous()：匿名访问：未登录可以访问，已登录不能访问
     * permitAll()：有没有认证都能访问：登录或未登录都能访问
     * denyAll(): 拒绝
     * authenticated()：认证之后才能访问
     * hasAuthority（）：包含权限
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                // 关闭csrf(前后端分离项目要关闭此功能）
                .csrf().disable()
                // 禁用session (前后端分离项目，不通过Session获取SecurityContext)
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                // 请求认证配置
                .authorizeRequests()
                // 允许匿名访问：未登录可以访问，已登录不能访问
//                .antMatchers("/login").anonymous() //登录页面,但是已经登录的用户不能访问
                // .antMatchers("/textMybatis").hasAuthority("system:dept:list22")
                // 任意用户，认证之后才可以访问（除上面外的）
                .anyRequest().authenticated();

        // 添加token过滤器
        http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
        // 配置异常处理器
        http.exceptionHandling()
                // 认证失败
                .authenticationEntryPoint(authenticationEntryPoint)
                // 授权失败
                .accessDeniedHandler(accessDeniedHandler);

        //开启登录
        http.formLogin()
                .loginProcessingUrl("/user/login") //自定义登录请求路径(post) 需要前端通过url请求无须任何参数
                .successHandler(successHandler) //验证成功处理器(前后端分离)：生成token及响应状态码200
                .failureHandler(failureHandler) //验证失败处理器(前后端分离)：返回状态码402
                .authenticationDetailsSource(authenticationDetailsSource); //身份验证详细信息源(登录验证中增加额外字段)
        // spring security 允许跨域
        http.cors();
    }


    //获取全部需要排除的url
    private  Set<String> getAuthorityUrls() {
        Map<String, AuthorityResource> beans = SpringContextUtil.getBeans(AuthorityResource.class);
        Set<String> authorityUrls = new HashSet<>();
        for (Map.Entry<String, AuthorityResource> entry : beans.entrySet()) {
            AuthorityResource value = entry.getValue();
            Set<String> authorityResource = value.getAuthorityResource();
            authorityUrls.addAll(authorityResource);
        }
        return authorityUrls;
    }
    //创建匹配器
     private List<RequestMatcher> createRequestMatcher(Set<String> authorityUrls) {
        List<RequestMatcher> requestMatchers = new ArrayList<>();
        for (String authorityUrl : authorityUrls) {
            requestMatchers.add(new AntPathRequestMatcher(authorityUrl));
        }
        return requestMatchers;
     }

     //获取全部@Controller注解修饰的类
    public  List<String> anonymousUrlAll() {

        Set<String> urlAll = new HashSet<>();
        Map<RequestMappingInfo, HandlerMethod> handlerMethods = requestMappingHandlerMapping.getHandlerMethods();
        for (Map.Entry<RequestMappingInfo, HandlerMethod> requestMappingInfoHandlerMethodEntry : handlerMethods.entrySet()) {

            //判断类上是否有@Anonymous注解或者方法上是否有@Anonymous注解
            Anonymous anonymous = requestMappingInfoHandlerMethodEntry.getValue().getBeanType().getAnnotation( Anonymous.class);
            if (anonymous != null) {
                urlAll.addAll(requestMappingInfoHandlerMethodEntry.getKey().getPatternsCondition().getPatterns());
            }
            Anonymous anonymous1 = requestMappingInfoHandlerMethodEntry.getValue().getMethod().getAnnotation( Anonymous.class);
            if (anonymous1 != null) {
                urlAll.addAll(requestMappingInfoHandlerMethodEntry.getKey().getPatternsCondition().getPatterns());
            }

        }
        return urlAll.isEmpty() ? new ArrayList<>() : new ArrayList<>(urlAll);
    }




}

